// Copyright (c) 2007-2008, 
// Trevor McCauley, http://www.senocular.com, 
// Alessandro Crugnola, http://www.sephiroth.it,
// Marc Speck, http://www.faindu.com
// All Rights Reserved. The following is Source Code and is subject to all restrictions 
// on such code as contained in the license accompanying this product.
package com.faindu.editImage.scale
{
	import com.faindu.editImage.EditImage;
	import com.faindu.editImage.base.BaseControl;
	import com.faindu.editImage.base.BaseTool;
	import com.faindu.editImage.base.EditImageEvent;
	import com.faindu.editImage.base.IEditImageTools;
	
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import mx.core.FlexSprite;

/**
 * Tool that scales the image in parallel to its original sides. By default, 
 * <code>ScaleTool</code> is disabled.
 */	
public class ScaleTool extends BaseTool
{
    //--------------------------------------------------------------------------  
    //
    //  Properties
    //
    //--------------------------------------------------------------------------  	
	/** @private */
	public var topLeftControl: BaseControl
	/** @private */
	public var topControl: BaseControl
	/** @private */
	public var topRightControl: BaseControl
	/** @private */
	public var rightControl: BaseControl
	/** @private */
	public var bottomRightControl: BaseControl
	/** @private */
	public var bottomControl: BaseControl
	/** @private */
	public var bottomLeftControl: BaseControl
	/** @private */
	public var leftControl: BaseControl

    //==========================================================================
    //
    //  Methods
    //
    //--------------------------------------------------------------------------  		
	/**
	 * @inheritDoc
	 */	
	override public function init( editImage:EditImage, startInteractionHandler:Function ): void
	{
		// initialize properties
		editImageTools = IEditImageTools( editImage )
		editImage.addEventListener( EditImageEvent.CONTROL_DOWN, startControlInteraction )
		
		// create all controls
		topLeftControl        = new ScaleControl( scaleTopLeftInteraction,    TOP_LEFT )
		topControl            = new ScaleControl( scaleTopInteraction,        TOP )
		topRightControl       = new ScaleControl( scaleTopRightInteraction,   TOP_RIGHT )
		rightControl          = new ScaleControl( scaleRightInteraction,      RIGHT )
		bottomRightControl    = new ScaleControl( scaleBottomRightInteraction,BOTTOM_RIGHT )
		bottomControl         = new ScaleControl( scaleBottomInteraction,     BOTTOM )
		bottomLeftControl     = new ScaleControl( scaleBottomLeftInteraction, BOTTOM_LEFT )
		leftControl           = new ScaleControl( scaleLeftInteraction,       LEFT )
		
		// initialize all controlls
		topLeftControl.init( 	editImage, startInteractionHandler, this )
		topControl.init( 		editImage, startInteractionHandler, this )
		topRightControl.init( 	editImage, startInteractionHandler, this )
		rightControl.init( 		editImage, startInteractionHandler, this )
		bottomRightControl.init(editImage, startInteractionHandler, this )
		bottomControl.init( 	editImage, startInteractionHandler, this )
		bottomLeftControl.init( editImage, startInteractionHandler, this )
		leftControl.init( 		editImage, startInteractionHandler, this )
	}

	/**
	 * @inheritDoc
	 */
	override public function draw(): void
	{
		topLeftControl.draw()
		topControl.draw()
		topRightControl.draw()
		rightControl.draw()
		bottomRightControl.draw()
		bottomControl.draw()
		bottomLeftControl.draw()
		leftControl.draw()
	}
	
	/**
	 * @inheritDoc
	 */
	override public function reset(): void
	{
		topLeftControl.reset()
		topControl.reset()
		topRightControl.reset()
		rightControl.reset()
		bottomRightControl.reset()
		bottomControl.reset()
		bottomLeftControl.reset()
		leftControl.reset()
	}
		
	//==========================================================================
    //
    //  Scale interactions
    //
    //--------------------------------------------------------------------------  
	/**
	 * The location of the mouse at the start of the interaction.
	 */
	private var interactionStart: Point
	
	/**
	 * The location of the mouse in the image coordinate system at the start of the interaction.
	 */
	private var innerInteractionStart: Point
	
	/**
	 * The <code>toolMatrix</code> at the start of the interaction
	 */
	private var interactionStartMatrix: Matrix

	/**
	 * The inverted <code>toolMatrix</code> at the start of the interaction.
	 */
	private var interactionStartInvertedMatrix: Matrix
	
	/**
	 * The bound of the imageHolder at the start of the interaction.
	 */
	private var interactionStartBound: Rectangle
		
	/**
	 * @inheritDoc
	 */
	override protected function startControlInteraction( editImageEvent:EditImageEvent ): void
	{
		interactionStart =  editImageEvent.mousePosition.clone()
		interactionStartMatrix =  editImageTools.toolMatrix.clone()
		
		interactionStartInvertedMatrix = interactionStartMatrix.clone()
		interactionStartInvertedMatrix.invert()
		innerInteractionStart = interactionStartInvertedMatrix.transformPoint( interactionStart ) 
		
		var imageHolder: FlexSprite = editImageTools.imageHolder
		interactionStartBound = imageHolder.getBounds( imageHolder )
	}

    //==========================================================================
    //
    //  Scale interactions left, top, right, bottom
    //
    //--------------------------------------------------------------------------  	
	/**
	* Scale left
	*/		
	protected function scaleLeftInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		var distortPoint:Point  = distortOffset(new Point( innerMouseLoc.x, innerInteractionStart.y), 
												innerInteractionStart.x - interactionStartBound.width )
		
		toolMatrix.a  += distortPoint.x
		toolMatrix.b  += distortPoint.y
		
		editImageTools.toolMatrix = moveForStandingStill( interactionStartBound.bottomRight.clone(), toolMatrix )
	}
	
	/**
	* Scale Top
	*/		
	protected function scaleTopInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		var distortPoint:Point = distortOffset(	new Point(innerInteractionStart.x,
												innerMouseLoc.y), 
												innerInteractionStart.y - interactionStartBound.height )
												
		toolMatrix.c  += distortPoint.x
		toolMatrix.d  += distortPoint.y
		
		editImageTools.toolMatrix = moveForStandingStill( interactionStartBound.bottomRight.clone(), toolMatrix )
	}
		
	/**
	* Scale Right
	*/		
	protected function scaleRightInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
 		var distortPoint:Point = distortOffset(new Point( 	innerMouseLoc.x, 
															innerInteractionStart.y ), 
												innerInteractionStart.x )
												
 		toolMatrix.a  += distortPoint.x
		toolMatrix.b  += distortPoint.y
		editImageTools.toolMatrix = toolMatrix
	}
	
	/**
	* Scale Bottom
	*/		
	protected function scaleBottomInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		var distortPoint:Point = distortOffset(	new Point(innerInteractionStart.x,
												innerMouseLoc.y), 
												innerInteractionStart.y )
		toolMatrix.c  += distortPoint.x
		toolMatrix.d  += distortPoint.y
		editImageTools.toolMatrix = toolMatrix
	}
	
    //==========================================================================
    //
    //  Scale interactions of corners, e.g. top left corner
    //
    //--------------------------------------------------------------------------  		
	/**
	 * Scale top left
	 */
	protected function scaleTopLeftInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		if ( ! editImageEvent.shiftKey) 
		{
			// how much the mouse has moved from starting the interaction
		var moved:Point = innerMouseLoc.subtract( innerInteractionStart )
			// the relationship of the start location to the registration point
			var regOffset:Point = innerInteractionStart.subtract( 
					new Point(interactionStartBound.width, interactionStartBound.height) )
			// find the ratios between movement and the registration offset
			var ratioH:Number = regOffset.x ? moved.x/regOffset.x : 0
			var ratioV:Number = regOffset.y ? moved.y/regOffset.y : 0
			// have the larger of the movement distances brought down
			// based on the lowest ratio to fit the registration offset
			if (ratioH > ratioV) 
				innerMouseLoc.x = innerInteractionStart.x + regOffset.x * ratioV
			else
				innerMouseLoc.y = innerInteractionStart.y + regOffset.y * ratioH
		}			
		
		var distortH:Point = distortOffset(	new Point(innerMouseLoc.x, innerInteractionStart.y), 
											innerInteractionStart.x - interactionStartBound.width )
		var distortV:Point = distortOffset(	new Point(innerInteractionStart.x, innerMouseLoc.y), 
											innerInteractionStart.y - interactionStartBound.height )
		
		toolMatrix.a += distortH.x
		toolMatrix.b += distortH.y
		toolMatrix.c += distortV.x
		toolMatrix.d += distortV.y
		
		editImageTools.toolMatrix = moveForStandingStill( interactionStartBound.bottomRight.clone(), toolMatrix )
	}
	
	/**
	* Scale top right
	*/
	protected function scaleTopRightInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		if ( ! editImageEvent.shiftKey) 
		{
			// how much the mouse has moved from starting the interaction
			var moved:Point = innerMouseLoc.subtract( innerInteractionStart )
			// the relationship of the start location to the registration point
			var regOffset:Point = innerInteractionStart.subtract( 
						new Point(0, interactionStartBound.height) )
			// find the ratios between movement and the registration offset
			var ratioH:Number = regOffset.x ? moved.x/regOffset.x : 0
			var ratioV:Number = regOffset.y ? moved.y/regOffset.y : 0
			// have the larger of the movement distances brought down
			// based on the lowest ratio to fit the registration offset
			if (ratioH > ratioV) 
				innerMouseLoc.x = innerInteractionStart.x + regOffset.x * ratioV
			else
				innerMouseLoc.y = innerInteractionStart.y + regOffset.y * ratioH
		}
		
		var distortH:Point = distortOffset(	new Point( innerMouseLoc.x, innerInteractionStart.y), 
											innerInteractionStart.x )
		var distortV:Point = distortOffset(	new Point( innerInteractionStart.x, innerMouseLoc.y), 
											innerInteractionStart.y - interactionStartBound.height )
		
		toolMatrix.a += distortH.x
		toolMatrix.b += distortH.y
		toolMatrix.c += distortV.x
		toolMatrix.d += distortV.y
		
		var interactionStartBoundBottomLeft: Point = new Point( interactionStartBound.x, interactionStartBound.bottom )
		editImageTools.toolMatrix = moveForStandingStill( interactionStartBoundBottomLeft, toolMatrix )
	}		
	
	/**
	* Scale bottom right
	*/
	protected function scaleBottomRightInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		if ( ! editImageEvent.shiftKey) 
		{
			// how much the mouse has moved from starting the interaction
			var moved:Point = innerMouseLoc.subtract( innerInteractionStart )
			// the relationship of the start location to the registration point
			var regOffset:Point = innerInteractionStart.subtract( new Point() )
			// find the ratios between movement and the registration offset
			var ratioH:Number = regOffset.x ? moved.x/regOffset.x : 0
			var ratioV:Number = regOffset.y ? moved.y/regOffset.y : 0
			// have the larger of the movement distances brought down
			// based on the lowest ratio to fit the registration offset
			if (ratioH > ratioV) 
				innerMouseLoc.x = innerInteractionStart.x + regOffset.x * ratioV
			else
				innerMouseLoc.y = innerInteractionStart.y + regOffset.y * ratioH
		}			
		
		var distortH:Point = distortOffset( new Point( innerMouseLoc.x, innerInteractionStart.y), 
											innerInteractionStart.x )
		var distortV:Point = distortOffset(	new Point( innerInteractionStart.x, innerMouseLoc.y), 
											innerInteractionStart.y )
		
		toolMatrix.a += distortH.x
		toolMatrix.b += distortH.y
		toolMatrix.c += distortV.x
		toolMatrix.d += distortV.y
		editImageTools.toolMatrix = toolMatrix
	}

	/**
	 * Scale bottom left
	 */
	protected function scaleBottomLeftInteraction( editImageEvent:EditImageEvent ): void
	{
		var toolMatrix: Matrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		if ( ! editImageEvent.shiftKey) 
		{
			// how much the mouse has moved from starting the interaction
			var moved:Point = innerMouseLoc.subtract( innerInteractionStart )
			// the relationship of the start location to the registration point
			var regOffset:Point = innerInteractionStart.subtract( 
								new Point( interactionStartBound.width, 0 ))
			// find the ratios between movement and the registration offset
			var ratioH:Number = regOffset.x ? moved.x/regOffset.x : 0
			var ratioV:Number = regOffset.y ? moved.y/regOffset.y : 0
			// have the larger of the movement distances brought down
			// based on the lowest ratio to fit the registration offset
			if (ratioH > ratioV) 
				innerMouseLoc.x = innerInteractionStart.x + regOffset.x * ratioV
			else
				innerMouseLoc.y = innerInteractionStart.y + regOffset.y * ratioH
		}			
		
		var distortH:Point = distortOffset(	new Point( innerMouseLoc.x, 
											innerInteractionStart.y), 
											innerInteractionStart.x - 
											interactionStartBound.width )
		var distortV:Point = distortOffset(	new Point( innerInteractionStart.x, 
											innerMouseLoc.y), 
											innerInteractionStart.y )
		
		toolMatrix.a += distortH.x
		toolMatrix.b += distortH.y
		toolMatrix.c += distortV.x
		toolMatrix.d += distortV.y
		
		var interactionStartBoundTopRight: Point = new Point( interactionStartBound.right, interactionStartBound.y )
		editImageTools.toolMatrix = moveForStandingStill( interactionStartBoundTopRight, toolMatrix )
	}	

    //==========================================================================
    //
    //  Helper functions for scale interactions
    //
    //--------------------------------------------------------------------------  	
	/**
	 * 
	 * @param offset is in the inner coordinate system
	 * @param regDiff
	 * @return 
	 */
	private function distortOffset( offset:Point, regDiff:Number ): Point 
	{
		var ratioH:Number = regDiff ? interactionStartBound.width/regDiff : 0
		var ratioV:Number = regDiff ? interactionStartBound.height/regDiff : 0
		var trans:Point = interactionStartMatrix.transformPoint( offset )
		offset = trans.subtract( interactionStart )
				
		offset.x *= interactionStartBound.width ? ratioH/interactionStartBound.width : 0
		offset.y *= interactionStartBound.height ? ratioV/interactionStartBound.height : 0
		return offset // this offset is in the parent coordinate system
	}

	/**
	 * When scaling e.g. the left side, the image needs to be moved in order to 
	 * have a fixed right side. This is because DisplayObject.scrollRect cuts out
	 * the invisible parts of the DisplayObject, in this case the image.
	 * 
	 * @param toolMatrix the updated matrix that that takes the new scaling into account.
	 * @return the moved toolMatrix
	 */
	protected function moveForStandingStill( interactionStartBoundCorner:Point, toolMatrix:Matrix ): Matrix
	{
		// transform interactionStartBoundCorner with the original and the newly scaled matrix
 		var interactionStartCorner: Point = interactionStartMatrix.transformPoint( interactionStartBoundCorner )
 		var newlyScaledCorner: Point = toolMatrix.transformPoint( interactionStartBoundCorner )
 		
 		// the difference of the to points gives the necessary movement for the image.
		var currentScaleDiff: Point = interactionStartCorner.subtract( newlyScaledCorner )
 		toolMatrix.tx += currentScaleDiff.x
 		toolMatrix.ty += currentScaleDiff.y 
 		
 		return toolMatrix
	}	
}
}